package sk.tuke.constraintbuilder.views.components;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceAdapter;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.wb.swt.SWTResourceManager;

import sk.tuke.constraintbuilder.editor.ConstraintEditorController;
import sk.tuke.constraintbuilder.utils.MyImages;

public class AnnotationChooserPanel extends Composite {
	
	private final FormToolkit formToolkit = new FormToolkit(Display.getDefault());
	private IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
	
	//Controller
	private ConstraintEditorController controller;
	
	//JFace components
	private TreeViewer viewer;
	private TreeParent invisibleRoot;
	
	//JDT components
 	private Set<IProject> projects = new HashSet<>();
 	private Set<IPath> jarLibraries = new HashSet<>();
 	

	/**
	 * Create the composite.
	 * @param parent
	 * @param style
	 */
	public AnnotationChooserPanel(final Composite parent, ConstraintEditorController controller) {
		super(parent, SWT.NONE);
		setBackground(SWTResourceManager.getColor(SWT.COLOR_WHITE));
		setLayout(new GridLayout(1, false));
		
		this.controller = controller;
		
		viewer = new TreeViewer(this, SWT.BORDER);
		final Tree tree = viewer.getTree();
		tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
		formToolkit.paintBordersFor(tree);		
		
        viewer.setContentProvider(new ViewContentProvider());
        viewer.setLabelProvider(new ViewLabelProvider());
        viewer.setInput(invisibleRoot);
        
        //DND Support
		int operations = DND.DROP_COPY| DND.DROP_MOVE;
		Transfer[] transferTypes = new Transfer[]{TextTransfer.getInstance()};
		DragSourceListener listener = new DragSourceAdapter() {
			@Override
			public void dragSetData(DragSourceEvent event) {
				DragSource ds = (DragSource) event.widget;
				if(ds.getControl() instanceof Tree){
					TreeItem[] items = ((Tree)ds.getControl()).getSelection();
					String value = items[0].getText();
					if (TextTransfer.getInstance().isSupportedType(event.dataType)) {
						event.data = value;
					}
				}
			}
		};
		viewer.addDragSupport(operations, transferTypes , listener);
        
        viewer.getTree().addListener(SWT.Resize, new Listener() {
            public void handleEvent(Event e) {
                parent.layout();
                parent.getParent().layout();
                parent.getParent().getParent().layout();               
            }
        });
        
        createTree();
	}	

	public void createTree(){
	    if(!loadLists()){
	    	loadProjectListFromWokspace();
	    }
		
		invisibleRoot = new TreeParent("");
		try {
			//Add annotations from jar libraries to tree
			for (IPath path : jarLibraries) {
    			IFile file = workspaceRoot.getFile(path);
    			IPackageFragmentRoot jar = JavaCore.createJarPackageFragmentRootFrom(file);
    			if(jar!=null){
    				TreeParent jarRoot = new TreeParent(jar.getElementName());
        			try {
    					for (IJavaElement element : jar.getChildren()) {
    						if(element instanceof IPackageFragment){
    							for (IClassFile classFile : ((IPackageFragment) element).getClassFiles()) {
    								for (IJavaElement javaElement : classFile.getChildren()){
    									IType type = (IType) javaElement;
    									if(type.isAnnotation()){
    										TreeObject obj = new TreeObject(type.getFullyQualifiedName());
    										jarRoot.addChild(obj);
    									}
    								}
    			    			}
    						}
    					}
    				} catch (JavaModelException e1) {
    					System.out.println("AnnotationChooserPanel: JavaModel Exception");
    				}
        			invisibleRoot.addChild(jarRoot);
    			}	
			}
//			Add annotations from projects to tree
			for (IProject project : projects) {
				if(project.isNatureEnabled("org.eclipse.jdt.core.javanature")){					
					TreeParent projectRoot = new TreeParent(project.getName());
					IJavaProject javaProject = JavaCore.create(project);
					javaProject.getPackageFragmentRoots();
					for (IPackageFragment pckg : javaProject.getPackageFragments()) {
						if(pckg.getKind() == IPackageFragmentRoot.K_SOURCE){
							for (ICompilationUnit cu : pckg.getCompilationUnits()) {
								for (IType type : cu.getAllTypes()){
									if(type.isAnnotation()){
										TreeObject obj = new TreeObject(type.getFullyQualifiedName());
										projectRoot.addChild(obj);
									}
									
								}
			    			}
						}
					}
					invisibleRoot.addChild(projectRoot);
				}
			}
			
		}catch (Exception e) {
			
			e.printStackTrace();
		}
		viewer.setInput(invisibleRoot);
		saveLists();
	}
	
	private void saveLists() {
		StringBuilder projectListSB = new StringBuilder("");
		for (IProject project : projects) {
			projectListSB.append(project.getName());
			projectListSB.append(';');
		}
		
		StringBuilder jarListSB = new StringBuilder("");
		for (IPath path : jarLibraries) {
			jarListSB.append(path.toString());
			jarListSB.append(';');
		}
		
		IEclipsePreferences preferences = ConfigurationScope.INSTANCE.getNode("sk.tuke.constraintbuilder");
		preferences.put("annotationpanel-projectlist", String.valueOf(projectListSB.toString()));
		preferences.put("annotationpanel-librarylist", String.valueOf(jarListSB.toString()));
		try {
			preferences.flush();
		} catch (org.osgi.service.prefs.BackingStoreException e) {
		}
	}
	
	
	private boolean loadLists() {
		IEclipsePreferences preferences = ConfigurationScope.INSTANCE.getNode("sk.tuke.constraintbuilder");
		String projectListString = preferences.get("annotationpanel-projectlist", "");
		String jarListString = preferences.get("annotationpanel-librarylist", "");

		StringTokenizer tokenizer = new StringTokenizer(projectListString, ";");
		while(tokenizer.hasMoreTokens()) { 
			IProject project = workspaceRoot.getProject(tokenizer.nextToken());
			if(project!=null)projects.add(project);
		} 
		
		tokenizer = new StringTokenizer(jarListString, ";");
		while(tokenizer.hasMoreTokens()) { 
			IPath path = new Path(tokenizer.nextToken());
			if(path!=null)jarLibraries.add(path);
		} 
		return true;
	}
	
	private void loadProjectListFromWokspace() {
		for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
			addProject((IProject) project);
		}
		
	}

	 public void addProject(IProject project){
		 this.projects.add(project);
	 }
	 
	 public void addJar(IPath jarPath){
		 this.jarLibraries.add(jarPath);
	 }
	 

	public void removeSelected() {
		ISelection selection = viewer.getSelection();
		if (selection != null && selection instanceof IStructuredSelection) {
			Object obj = ((IStructuredSelection) selection).getFirstElement();
			if(obj instanceof TreeParent){
				invisibleRoot.removeChild((TreeParent) obj);
				viewer.refresh();
				String parentName = ((TreeParent) obj).getName();
				IProject project = workspaceRoot.getProject(parentName);
				if(project!=null){
					projects.remove(project);
				}
				
				//TODO: Remove path
//				IPath path = new Path(parentName);
//				if(path!=null){
//					jarLibraries.remove(path);
//				}
				saveLists();
			}
		}
	}
   
	/**
	 * Content provider for AnnotationTreeViewer
	 */
   class ViewContentProvider implements ITreeContentProvider {
       public void inputChanged(Viewer v, Object oldInput, Object newInput) {}
       public void dispose() {}

       public Object[] getElements(Object parent) {
    	   if(parent.equals(invisibleRoot))return getChildren(invisibleRoot);
    	   return getChildren(parent);
       }

       public Object getParent(Object child) {
    	   if(child instanceof TreeObject)return ((TreeObject) child).getParent();
    	   return null;
       }

       public Object[] getChildren(Object parent) {
    	   if(parent instanceof TreeParent) {
    		   TreeObject[] children = ((TreeParent) parent).getChildren();
    		   ArrayList<Object> childrenList = new ArrayList<>();
    		   for (TreeObject treeObject : children) {
    			   if(treeObject instanceof TreeParent){
    				   if(!((TreeParent)treeObject).hasChildren())continue;
    			   }
    			   childrenList.add(treeObject);
    		   }
    		   return childrenList.toArray();
    	   }
    	   return new Object[0];
       }

       public boolean hasChildren(Object parent) {
    	   if(parent instanceof TreeParent)return ((TreeParent) parent).hasChildren();
    	   return false;
       }
   }

	/**
	 * Label provider for AnnotationTreeViewer
	 */
   class ViewLabelProvider extends LabelProvider {
	   public String getText(Object obj) {
		   return obj.toString();
       }
	   
       public Image getImage(Object obj) {
    	   Image image = MyImages.ANNOTATION;
    	   if (obj instanceof TreeParent)
    		   image = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER);
    	   return image;
       }
   }
}

class TreeObject implements IAdaptable {
    private String name;
    private TreeParent parent;
    public TreeObject(String name) { this.name = name; }
    public String getName() { return name; }
    public void setParent(TreeParent parent) { this.parent = parent; }
    public TreeParent getParent() { return parent; }
    public String toString() { return getName(); }
    public Object getAdapter(Class key) { return null; }
}

class TreeParent extends TreeObject {
    private ArrayList children;
    public TreeParent(String name) { super(name); children = new ArrayList(); }
    public void addChild(TreeObject child) { children.add(child); child.setParent(this); }
    public void removeChild(TreeObject child) { children.remove(child); child.setParent(null); }
    public TreeObject[] getChildren() { return (TreeObject[]) children.toArray(new TreeObject[children.size()]); }
    public boolean hasChildren() { return children.size() > 0; }
}
